﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Amie
{
    /// <summary>
    /// The main installation utility.  Updates the web and services provided in the ProductInfo object.
    /// </summary>
    public class ApplicationHelper
    {
        ProductInfo _info;
        InstallResult _installResult;
      
        public ApplicationHelper(ProductInfo info)
        {
            _info = info;
        }

        public InstallResult RunApplicationUpdates()
        {
            _installResult = new InstallResult();

            //This will be set to false on failures.
            _installResult.Success = true;

            //Changed this to allow an override.  If the package just need to be moved to a different directory they can copy the installer and run it again even if all of the versions are the same.
            //if (!_info.UpdateRequired) return _installResult;

            _installResult = UpdateDatabase();
            if (_installResult.Success)
            {
                HandleWebInstall();
                HandleServiceInstall();
            }
            return _installResult;
        }

        private void HandleWebInstall()
        {
            if (_info.WebIsInstalled)
                PerformWebUpdate();
            else
                PerformNewWebInstall();
        }

        private void PerformWebUpdate()
        {
            Logger.Info("Updating web app {0}", _info.WebApplicationName);
            //In an update we don't copy anything or move anything except settings. Where ever the installer(human) puts the folder is where it will live. We just keep moving the path of the web and path of the service to the new spot.
            CopyConfigSettings(_info.WebInstalledConfigPath, _info.WebUpdateConfigPath);

            Logger.Info("Creating application pool: {0}", _info.WebApplicationPoolName);
            IISHelper.CreateApplicationPool(_info.WebApplicationPoolName);

            Logger.Info("Creating application {0} in website {1}", _info.WebApplicationName, _info.WebSite.Name);
            IISHelper.ChangeApplicationPath(_info.WebSite.Name, _info.WebApplicationName, _info.WebUpdatePath);

            Logger.Info("Setting Application Pool {0} in application {1}", _info.WebApplicationPoolName, _info.WebApplicationName);
            IISHelper.SetApplicationApplicationPool(_info.WebSite.Name, _info.WebApplicationName, _info.WebApplicationPoolName);

            Logger.Info("Update Finished!");
        }

        private void CopyConfigSettings(string source, string destination)
        {
            ExeConfigurationFileMap installedConfigFile = new ExeConfigurationFileMap();
            installedConfigFile.ExeConfigFilename = source; //_info.WebConfigPathInstalled;
            Configuration configInstalled = ConfigurationManager.OpenMappedExeConfiguration(installedConfigFile, ConfigurationUserLevel.None);

            ExeConfigurationFileMap updateConfigFile = new ExeConfigurationFileMap();
            updateConfigFile.ExeConfigFilename = destination; //_info.WebConfigPathUpdate;
            Configuration configUpdate = ConfigurationManager.OpenMappedExeConfiguration(updateConfigFile, ConfigurationUserLevel.None);

            foreach (KeyValueConfigurationElement appSetting in configInstalled.AppSettings.Settings)
            {
                if (configUpdate.AppSettings.Settings[appSetting.Key] != null)
                {
                    configUpdate.AppSettings.Settings[appSetting.Key].Value = appSetting.Value;
                }
            }

            configUpdate.ConnectionStrings.ConnectionStrings[_info.ConnectionStringName].ConnectionString = _info.ConnectionString.ConnectionString;


            //foreach (System.Configuration.ConnectionStringSettings conStr in configInstalled.ConnectionStrings.ConnectionStrings)
            //{              
            //    if (configUpdate.ConnectionStrings.ConnectionStrings[conStr.Name]  != null)
            //    {
            //        configUpdate.ConnectionStrings.ConnectionStrings[conStr.Name].ConnectionString = conStr.ConnectionString;
            //    }
            //}

            configUpdate.Save(ConfigurationSaveMode.Modified);
            Logger.Info("Copy Config Settings: Done");
        }

        private void SetConnectionString(string configFilePath)
        {
            ExeConfigurationFileMap updateConfigFile = new ExeConfigurationFileMap();
            updateConfigFile.ExeConfigFilename = configFilePath; //_info.WebConfigPathUpdate
            Configuration configUpdate = ConfigurationManager.OpenMappedExeConfiguration(updateConfigFile, ConfigurationUserLevel.None);
            configUpdate.ConnectionStrings.ConnectionStrings[_info.ConnectionStringName].ConnectionString = _info.ConnectionString.ConnectionString;
            configUpdate.Save(ConfigurationSaveMode.Modified);
        }
    
        private void PerformNewWebInstall()
        {
            Logger.Info("Installing new web app {0}", _info.WebApplicationName);

            Logger.Info("Creating application pool: {0}", _info.WebApplicationPoolName);
            IISHelper.CreateApplicationPool(_info.WebApplicationPoolName);

            Microsoft.Web.Administration.Site webSite = IISHelper.GetFirstSite();

            if (webSite == null)
            {
                _installResult.Success = false;
                _installResult.Message = "The first website could not be found.  The application has not been installed.";
                return;
            }

            Logger.Info("Creating application {0} in website {1}", _info.WebApplicationName, webSite.Name);
            IISHelper.CreateApplication(webSite.Name, _info.WebApplicationName, _info.WebUpdatePath);

             Logger.Info("Setting Application Pool {0} in application {1}", _info.WebApplicationPoolName, _info.WebApplicationName);
            IISHelper.SetApplicationApplicationPool(webSite.Name, _info.WebApplicationName, _info.WebApplicationPoolName);

             Logger.Info("Setting Web Connection String");
            SetConnectionString(_info.WebUpdateConfigPath);

            Logger.Info("Installation Finished!");
        }

        private void HandleServiceInstall()
        {
            if (_info.ServiceIsInstalled)
                PerformServiceUpdate();
            else
               PerformNewServiceInstall();
        }

        private bool PerformServiceUpdate()
        {
            Logger.Info("Updating service {0} at {1}", _info.ServiceName, _info.ServiceUpdatePath);
            if (StopService())
            {
                CopyConfigSettings(_info.ServiceInstalledConfigPath, _info.ServiceUpdateConfigPath);

                if (!UninstallOldService()) return false;

                if (!InstallNewService()) return false;

                //If this fails, don't error, just keep moving on, nothing to see here.
                StartService();

                Logger.Info("Service update finshed!");
                return true;
            }
            Logger.Error("The service {0} could not be stopped.", _info.ServiceName);
            _installResult.Success = false;
            _installResult.Message = string.Format("The service {0} could not be stopped.", _info.ServiceName);
            return false;
        }

        private void PerformNewServiceInstall()
        {
            Logger.Info("Installing new service {0}", _info.ServiceName);
            InstallNewService();
            SetConnectionString(_info.ServiceUpdateConfigPath);
            StartService();
            Logger.Info("Service instllation complete!");
        }

        private bool StopService()
        {
            //Service does not exist
            if (_info.Service == null) return true;

            if (_info.Service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped) return true;

            _info.Service.Stop();
            return true;
        }

        private bool StartService()
        {
            //Service does not exist
            if (_info.Service == null) return true;

            if (_info.Service.Status == System.ServiceProcess.ServiceControllerStatus.Running) return true;

            try
            {
                _info.Service.Start();
            }
            catch (Exception ex)
            {
                Logger.Info("The service was installed but not started becuase of an error: {0}.", ex.ToString());
            }
            
            return true;
        }

        private bool InstallNewService()
        {
            Logger.Info("Installing Service at {0}", _info.ServiceUpdatePath);
            return CallInstallUtil(new string[] {"/LogFile=InstallTrafficService.log", _info.ServiceUpdatePath });
        }

        private bool UninstallOldService()
        {
            //Another way to do it if InstallUtil.exe is not installed on the system, but I could never get it to work 2/21/2015.  Can be used to call install as well without the /u
            //System.Configuration.Install.ManagedInstallerClass.InstallHelper(new string[] { Assebly.GetExecutingAsseembly().Location });

            //Attempt to uninstall from installed location.  If this does not work then use the update location to try uninstall. If this is already installed it should not matter.
            try
            {
                Logger.Info("Uninstalling Service at {0}", _info.ServiceInstalledPath);
                var result = CallInstallUtil(new string[] { "/u", "/LogFile=UninstallTrafficService.log", _info.ServiceInstalledPath });
                if (result)
                    return true;
                else
                    throw new Exception("Failed to uninstall the service");

            }
            catch (Exception)
            {
                try
                {
                    Logger.Info("Uninstalling the service from the installed location {0} failed, so we are trying at the update location {1}", _info.ServiceInstalledPath,  _info.ServiceUpdatePath);
                    var result = CallInstallUtil(new string[] { "/u", "/LogFile=UninstallTrafficService.log", _info.ServiceUpdatePath });
                    if (result)
                    {
                        _installResult.Success = true;
                        return true;
                    }
                    return false;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
           
        }

        internal InstallResult UpdateDatabase()
        {
            DB.SchemaChangeApi schema = new DB.SchemaChangeApi(_info);
            return schema.CreateDatabase();
        }

        private static string InstallUtilPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
        private bool CallInstallUtil(string[] installUtilArguments)
        {
            Process proc = new Process();
            proc.StartInfo.FileName = Path.Combine(InstallUtilPath, "installutil.exe");
            proc.StartInfo.Arguments = String.Join(" ", installUtilArguments);
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.UseShellExecute = false;

            proc.Start();
            string outputResult = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit();

            //  ---check result---
            if (proc.ExitCode != 0)
            {
                Logger.Error("Error with the service.  Here is the error {0}", outputResult);
                _installResult.Success = false;
                _installResult.Message = outputResult;
                return false;
            }

            return true;
        } 
   }
}
